package com.eyas.im.common.datasource.impl;

import com.eyas.im.common.datasource.RowMapper;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class DefaultRowMapper implements RowMapper {
    static {
        RowMapper.PRIMITIVE_CLASS_SETS.add(byte.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Byte.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(short.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Short.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(int.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Integer.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(long.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Long.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(float.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Float.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(double.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Double.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(boolean.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Boolean.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(char.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Character.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(String.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(Date.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(java.sql.Date.class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(byte[].class);
        RowMapper.PRIMITIVE_CLASS_SETS.add(BigDecimal.class);
    }


    private final ResultSetMetaData resultSetMetaData;
    private final Set<String> columnNameSet = new IgnoreCaseHashSet();
    public DefaultRowMapper(ResultSetMetaData resultSetMetaData) throws SQLException {
        this.resultSetMetaData = resultSetMetaData;
        for(int i=1;i<=this.resultSetMetaData.getColumnCount();i++) {
            this.columnNameSet.add(this.resultSetMetaData.getColumnLabel(i));
        }
    }
    /**
     * 为objClazz以及superClass能够映射的属性赋值，必须包含public修饰的setter方法
     * @param rawData
     * @param objClazz
     * @param <T>
     * @return 返回赋值后的对象
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    @Override
    public <T> T rawDataToObject(ResultSet rawData, Class<T> objClazz) throws Exception {
        assert this.resultSetMetaData!=null;
        assert rawData!=null;
        assert objClazz!=null;
        if(RowMapper.PRIMITIVE_CLASS_SETS.contains(objClazz)) {
            return (T)this.primitiveMapper(rawData,objClazz);
        }
        Constructor<T> constructor = objClazz.getConstructor();
        constructor.setAccessible(true);
        T result = constructor.newInstance();
        Class<?> clazz = objClazz;
        while(clazz!=Object.class) {
            Method[] allMethods = clazz.getDeclaredMethods();
            if(allMethods.length==0) {
                clazz = clazz.getSuperclass();
                continue;
            }
            //缓存method信息，提高后面的查询效率
            Map<String,Method> methodMap = new HashMap<>();
            for(int i=0;i<allMethods.length;i++) {
                String methodName = allMethods[i].getName();
                if(methodName.startsWith("set")&&allMethods[i].getModifiers()== Modifier.PUBLIC) {
                    StringBuilder methodNameNew = new StringBuilder(methodName).append("(");
                    for(Class<?> parameterType : allMethods[i].getParameterTypes()) {
                        methodNameNew.append(parameterType.getName()).append(",");
                    }
                    methodNameNew.append(")");
                    methodMap.put(methodNameNew.toString(),allMethods[i]);
                }
            }

            Field[] allFields = clazz.getDeclaredFields();//获取objClazz所有的成员属性，不包含父类的
            for(int i=0;i<allFields.length;i++) {
                String fieldName = allFields[i].getName();
                //对比columnNameSet名称是否与fieldName一致，不在乎大小写
                if(this.columnNameSet.contains(fieldName)) {
                    //构造methodName：setXxxx()
                    Class<?> fieldType = allFields[i].getType();
                    char[] chars = fieldName.toCharArray();
                    if(chars[0]>='a'&&chars[0]<='z')
                        chars[0] -= 32;
                    String setterName = "set"+String.valueOf(chars)+"("+fieldType.getName()+",)";
                    //获取对应的method对象
                    Method method = methodMap.get(setterName);
                    if(method==null)
                        continue;
                    //执行赋值操作
                    method.invoke(result,DefaultRowMapper.getRawData(rawData,fieldName,fieldType));
                }
            }
            clazz = clazz.getSuperclass();
        }
        return result;
    }

    /**
     * 基本数据类型映射
     * @param rawData
     * @param returnType
     * @return
     */
    private Object primitiveMapper(ResultSet rawData, Class<?> returnType) throws SQLException {
        if(returnType==byte.class||returnType==Byte.class) {
            return rawData.getByte(1);
        }else if(returnType==short.class||returnType==Short.class) {
            return rawData.getShort(1);
        }else if(returnType==int.class||returnType==Integer.class||
                returnType==char.class||returnType==Character.class) {
            return rawData.getInt(1);
        }else if(returnType==long.class||returnType==Long.class) {
            return rawData.getLong(1);
        }else if(returnType==float.class||returnType==Float.class) {
            return rawData.getFloat(1);
        }else if(returnType==double.class||returnType==Double.class) {
            return rawData.getDouble(1);
        }else if(returnType==boolean.class||returnType==Boolean.class) {
            return rawData.getBoolean(1);
        }else if(returnType==byte[].class) {
            return rawData.getBytes(1);
        }else if(returnType==String.class) {
            return rawData.getString(1);
        }else if(returnType==Date.class||returnType==java.sql.Date.class) {
            return rawData.getDate(1);
        }else if(returnType== BigDecimal.class) {
            return rawData.getBigDecimal(1);
        }else
            return rawData.getObject(1);
    }

    private static Object getRawData(ResultSet rawData,String fieldName,Class<?> returnType) throws SQLException {
        if(returnType==byte.class||returnType==Byte.class) {
            return rawData.getByte(fieldName);
        }else if(returnType==short.class||returnType==Short.class) {
            return rawData.getShort(fieldName);
        }else if(returnType==int.class||returnType==Integer.class||
                returnType==char.class||returnType==Character.class) {
            return (char)rawData.getInt(fieldName);
        }else if(returnType==long.class||returnType==Long.class) {
            return rawData.getLong(fieldName);
        }else if(returnType==float.class||returnType==Float.class) {
            return rawData.getFloat(fieldName);
        }else if(returnType==double.class||returnType==Double.class) {
            return rawData.getDouble(fieldName);
        }else if(returnType==boolean.class||returnType==Boolean.class) {
            return rawData.getBoolean(fieldName);
        }else if(returnType==byte[].class) {
            return rawData.getBytes(fieldName);
        }else if(returnType==String.class) {
            return rawData.getString(fieldName);
        }else if(returnType==Date.class||returnType==java.sql.Date.class) {
            return rawData.getDate(fieldName);
        }else if(returnType== BigDecimal.class) {
            return rawData.getBigDecimal(fieldName);
        }else
            return rawData.getObject(fieldName);
    }
}
